//============================================================================
// Name        : paper-jets.cpp
// Author      : Vector Game Studio
// Version     : 0.0.4
// Copyright   : Vector Game Studio 2011 (c)
// Description : Hello World in C++, Ansi-style
//============================================================================

#include "Camera.hpp"
#include "FrameBuffer.hpp"
//#define DEBUG
//#define DEBUG_DRAW

#define UINT unsigned int

#define MOUSE_SENSETIVITY 0.5f

#define WINDOW_WIDTH 1024
#define WINDOW_HEIGHT 1024
#define VERTEX_COUNT 5
#define INDEX_COUNT 9
#define TEXTURE_COUNT 1
#define OBJECTS_COUNT 2

#define MULTISAMPLES_ENABLE // Включает сглаживание контуров по средствам мультисемплинга

#define VAR_TO_STR(variable) #variable
#define GLF_BUFFER_OFFSET(i) ((char *)NULL + (i))

#define ELEMENTS_IN_STRUCTURE(structure,elmtype) sizeof(structure) / sizeof(elmtype)

struct Vertex
{
  Vertex()/*:position(0.0f,0.0f,0.0f),normal(0.0f,0.0f,0.0f),texture(0.0f,0.0f)*/{}; // TO DO ------------------------------------------------
  Vertex( glm::vec3 Position, glm::vec3 Normal, glm::vec2 Texture) : position(Position), normal(Normal), texture(Texture) {}
  glm::vec3 position;
  glm::vec3 normal;
  glm::vec2 texture;
};

typedef struct SmdModel
{
	Vertex* vertices;
	GLushort *indexes;
	UINT vertexCount;
	GLsizei triangleCount;

	SmdModel():vertexCount(0),triangleCount(0){};
	virtual ~SmdModel(){
							if ( NULL != vertices ) delete[] vertices;
							if ( NULL != indexes ) delete[] indexes;
						};
}SmdModel, *PSmdModel;


  GLuint          VertexArrayName;
  volatile bool   bDrawGo = true;
  GLuint TextureNames[TEXTURE_COUNT];
  GLuint ProgramName;
  GLint UniformMVP;
  GLint UniformPos;
  GLuint TextureName;
  GLint UniformLightDir;

  glm::vec3 LightDirection = glm::vec3( 0.5f, 0.5f, 0.0f);
  SmdModel *LoadedModel;
  glm::mat4 ModelPositions[OBJECTS_COUNT];

  CGameCamera GameCamera;
  CFrameBuffer *MyFrameBuffer;
  CColorRenderBuffer *MyRenderBuffer;
  CTextureRenderBuffer *MyTextureBuffer;

void hello_world()
{
	fprintf(stdout, "Thread works!\n");
}

void Draw()
{
#ifdef DEBUG_DRAW
		fprintf(stdout, "Begin draw!\n");
#endif
	for ( int i = 0; i < 2; i++ )
	{
	    GLuint CurrentTextureName = TextureName;
		if ( 0 == i )
		{
			MyFrameBuffer->BindFrameBuffer();
		}
		else
		{
			MyFrameBuffer->BindFrameBuffer( false );
			MyTextureBuffer->UpdateBuffer();
			CurrentTextureName = MyTextureBuffer->GetBufferName();
		}
		if (!MyFrameBuffer->CheckFrameBuffer())
			fprintf( stdout, "Fuck your frame buffer i = %d\n", i );

		glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
		glm::mat4 View = GameCamera.GetViewMatrix();

		glViewport(0, 0, 1280,1024);

		glClearColor( 0.3f, 0.3f, 0.0f, 1.0f );
		glClearDepth( 1.0f );
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glUseProgram(ProgramName);

		for (int i = 0; i < OBJECTS_COUNT; i++ )
		{
			glm::mat4 MVP = Projection * View * ModelPositions[i];

			glUniformMatrix4fv( UniformMVP, 1, GL_FALSE, glm::value_ptr(MVP) );
			glUniformMatrix4fv( UniformPos, 1, GL_FALSE, glm::value_ptr(ModelPositions[i]));

			glUniform3fv( UniformLightDir, 1, &LightDirection[0]);

			glActiveTexture(GL_TEXTURE0);
			glBindTexture(GL_TEXTURE_2D, CurrentTextureName );

			glBindVertexArray(VertexArrayName);

			glDrawRangeElements(GL_TRIANGLES, 0, 100, (LoadedModel->triangleCount * 3), GL_UNSIGNED_SHORT, LoadedModel->indexes );//GL_TRIANGLE_STRIP
		}

		glUseProgram(0);
	}
	glFlush(); // Не уверен что это нужно, см http://www.opengl.org/sdk/docs/man/
	SDL_GL_SwapBuffers();
#ifdef DEBUG_DRAW
		fprintf( stdout, "End draw!\n" );
#endif
}



Vertex
ParseVertex( const char* VertString )
{
	Vertex Result;
	int TempNull;
	float TempNullF;

	sscanf( VertString, "%d %f %f %f %f %f %f %f %f %d %d %f", &TempNull,
			&Result.position.x, &Result.position.y, &Result.position.z,
			&Result.normal.x, &Result.normal.y, &Result.normal.z,
			&Result.texture.x, &Result.texture.y,
			&TempNull, &TempNull, &TempNullF);

	Result.position.x /= 20.0f;
	Result.position.y /= 20.0f;
	Result.position.z /= 20.0f;
	Result.normal.x /= 20.0f;
	Result.normal.y /= 20.0f;
	Result.normal.z /= 20.0f;

	return Result;
}

SmdModel*
CompressBuffersToModel( const Vertex *Vertices, const UINT VertCount )
{
	SmdModel* Result = new SmdModel();
	Result->triangleCount = VertCount / 3;
	Result->indexes = new GLushort[VertCount];
	memset( Result->indexes, 0xFFFF, sizeof(GLushort) * VertCount );

	Vertex CompressedVertices[VertCount];
	GLushort CurrentIndex = 0;

	for ( UINT i = 0; i < VertCount; i++ )
	{
        if ( 0xFFFF == Result->indexes[i] )
        {
        	Vertex tmpVertex = Vertices[i];
        	Result->indexes[i] = CurrentIndex;
        	for ( UINT j = (i+1); j < VertCount; j++ )
        	{
        		if (( Vertices[j].position.x == tmpVertex.position.x ) &&
        			( Vertices[j].position.y == tmpVertex.position.y ) &&
        			( Vertices[j].position.z == tmpVertex.position.z ))
        		{
        			Result->indexes[j] = CurrentIndex;

        		}
        	}
        	CompressedVertices[CurrentIndex] = tmpVertex;
        	CurrentIndex++;
        }
	}

	Result->vertices = new Vertex[CurrentIndex];
	Result->vertexCount = CurrentIndex;

	memcpy( (void*)Result->vertices, (void*)CompressedVertices, sizeof(Vertex)*CurrentIndex );
    //delete[] CompressedVertices;

	return Result;
}

SmdModel*
LoadSMDModel( const char* FileName )
{
	SmdModel* Result = NULL;

	char buffer[1024];
	Vertex* TempVertex = NULL;
	UINT TriangleCount = 0;

	FILE *file = fopen(FileName,"rt");

	if (file != NULL)
	while (fgets(buffer,1023,file))
	{
		buffer[strlen(buffer)-2] = '\0'; //Толи в линуксе дело, то ли в gcc на конц строки \r \n
		if ((strcmp(buffer,"triangles") == 0) && (0 == TriangleCount))
			while (fgets(buffer,1023,file))
			{
				buffer[strlen(buffer)-2] = '\0';
				if(strcmp(buffer,"end") == 0)
				{
					TempVertex = new Vertex[TriangleCount*3];
					rewind(file);
					break;
				}
				fgets(buffer,1023,file);
				fgets(buffer,1023,file);
				fgets(buffer,1023,file);
				TriangleCount++;
			}
		//----   ---------------------------------
		if (strcmp(buffer,"triangles") == 0)
		{
			for (UINT i = 0; i < TriangleCount ;i++)
			{
				fgets(buffer,1023,file);

				for ( int j = 0; j < 3; j++ )
				{
					fgets(buffer,1023,file);
					TempVertex[i*3+j] = ParseVertex( buffer );
				}
			}

			Result = CompressBuffersToModel( TempVertex, TriangleCount * 3 );
		}
	}
	if ( NULL != TempVertex )
	    delete[] TempVertex;

	fclose(file);
	file = NULL;
	return Result;
}

bool LoadTextue()
{
	glActiveTexture(GL_TEXTURE0);
	//if 	(GL_INVALID_ENUM == )
	{
		fprintf(stdout, "glActiveTexture error\n");
		return false;
	}

	glGenTextures( TEXTURE_COUNT, TextureNames);

	return true;
}

bool checkProgram(GLuint ProgramName)
{
	if(!ProgramName)
	  return false;

	GLint Result = GL_FALSE;
	glGetProgramiv(ProgramName, GL_LINK_STATUS, &Result);

	fprintf(stdout, "Linking program\n");
	int InfoLogLength;
	glGetProgramiv(ProgramName, GL_INFO_LOG_LENGTH, &InfoLogLength);
	std::vector<GLchar> Buffer(std::max(InfoLogLength, int(1)));
	glGetProgramInfoLog(ProgramName, InfoLogLength, NULL, &Buffer[0]);
	fprintf(stdout, "%s\n", &Buffer[0]);

	return Result == GL_TRUE;
}

bool checkShader(GLuint ShaderName, const char* Source)
{
	if(!ShaderName)
		return false;

	GLint Result = GL_FALSE;
	glGetShaderiv(ShaderName, GL_COMPILE_STATUS, &Result);

	fprintf(stdout, "Compiling shader\n%s...\n", Source);
	int InfoLogLength;
	glGetShaderiv(ShaderName, GL_INFO_LOG_LENGTH, &InfoLogLength);
	std::vector<char> Buffer(InfoLogLength);
	glGetShaderInfoLog(ShaderName, InfoLogLength, NULL, &Buffer[0]);
	fprintf(stdout, "%s\n", &Buffer[0]);

	return Result == GL_TRUE;
}

std::string loadFile(std::string const & Filename)
{
	std::ifstream stream(Filename.c_str(), std::ios::in);
	if(!stream.is_open())
			return "";

	std::string Line = "";
	std::string Text = "";

	while(getline(stream, Line))
		Text += "\n" + Line;

	stream.close();

	return Text;
}

GLuint createShader( GLenum Type, std::string const & Source )
{
	//bool Validated = true;
	GLuint Name = 0;

	if(!Source.empty())
	{
		std::string SourceContent = loadFile(Source);
		char const * SourcePointer = SourceContent.c_str();
		Name = glCreateShader(Type);
		glShaderSource(Name, 1, &SourcePointer, NULL);
		glCompileShader(Name);
		//Validated = checkShader(Name, SourcePointer);
	}

	return Name;
}

void
FrameBufferInitialization()
{
    MyFrameBuffer = new CFrameBuffer();
    MyTextureBuffer =new CTextureRenderBuffer();
    MyTextureBuffer->TuneRenderBuffer( GL_RGBA, WINDOW_WIDTH, WINDOW_HEIGHT );

    MyFrameBuffer->BindFrameBuffer(true);
    MyRenderBuffer = new CColorRenderBuffer();
    MyRenderBuffer->TuneRenderBuffer( GL_DEPTH_COMPONENT, WINDOW_WIDTH, WINDOW_HEIGHT );
    MyFrameBuffer->SetRenderTarget( GL_COLOR_ATTACHMENT0, MyTextureBuffer );
    MyFrameBuffer->SetRenderTarget( GL_DEPTH_ATTACHMENT, MyRenderBuffer );

    MyFrameBuffer->BindFrameBuffer(false);
}

int main ( int argc, char **argv )
{

    if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 )
    {
        fprintf ( stderr, "Error while initializing SDL: %s\n", SDL_GetError() );
        exit ( 1 );
    }

   // SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1 );	// использовать двойной буфер
   // SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 0);
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);	// размер в битах цветовых составляющих буфера
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);	// *---
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);	// *---
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,8);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,      16);
    SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,     0);//16

    SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE,  0);//8
    SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE,    0);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 0);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE,    0);

#ifdef MULTISAMPLES_ENABLE
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS,  1);
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,  4);
#endif

    if ( SDL_SetVideoMode ( WINDOW_WIDTH, WINDOW_HEIGHT, 32,  SDL_HWSURFACE | SDL_OPENGL ) == NULL ) //1152, 864, | SDL_FULLSCREEN
    {
        printf ( "Error while setting up video mode: %s\n", SDL_GetError() );
        exit ( 1 );
    }

    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); //Задаёт автоматическую посылку сообщений от клавиатуры если клавиша зажата

    //glDepthFunc ( GL_LESS );
    glEnable ( GL_DEPTH_TEST );

    GLuint VertexShaderName = createShader( GL_VERTEX_SHADER, "./shaders/VertexShader.vert" );
    GLuint FragmentShaderName = createShader(GL_FRAGMENT_SHADER, "./shaders/FragmentShader.frag" );

   if ( !checkShader( VertexShaderName, "VertexShader.vert" ) )
	   exit ( 1 );

   if ( !checkShader( FragmentShaderName, "FragmentShader.frag" ))
	  exit ( 1 );

    ProgramName = glCreateProgram();
    glAttachShader(ProgramName, VertexShaderName);
    glAttachShader(ProgramName, FragmentShaderName);
    glDeleteShader(VertexShaderName);
    glDeleteShader(FragmentShaderName);
    glLinkProgram( ProgramName );
    if (!checkProgram( ProgramName ))
    {
    	fprintf( stdout, "Fuck!!\n" );
    	return 0;
    }

    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR );

    TextureName = gli::createTexture2D("./textures/cat_3.dds");

    UniformMVP = glGetUniformLocation(ProgramName, "MVP");
    UniformPos = glGetUniformLocation(ProgramName, "POS");
    UniformLightDir = glGetUniformLocation(ProgramName, "LightDir");

//----------------------Model--------------------------------
#ifdef DEBUG_DRAW
    fprintf( stdout, "Before model load\n" );
#endif
    LoadedModel = LoadSMDModel("./models/sphere.SMD");
#ifdef DEBUG
    LoadedModel = new SmdModel();
    LoadedModel->triangleCount = INDEX_COUNT / 3;
    LoadedModel->vertexCount = VERTEX_COUNT;
    LoadedModel->indexes = new GLushort[INDEX_COUNT];
    memcpy( (void*)LoadedModel->indexes, (void*)Indices, sizeof(GLushort) * INDEX_COUNT );
    LoadedModel->vertices = new Vertex[VERTEX_COUNT];
    memcpy( (void*)LoadedModel->vertices, (void*)Vertexes, sizeof(Vertex) * VERTEX_COUNT );
#endif
#ifdef DEBUG_DRAW
    fprintf( stdout, "LoadSMDModel(/models/cube2.SMD);\n" );
#endif

#ifdef DEBUG_DRAW
    fprintf( stdout, "%s = %u\n", VAR_TO_STR(LoadedModel->vertexCount), LoadedModel->vertexCount );
    fprintf( stdout, "%s = %u\n", VAR_TO_STR(LoadedModel->triangleCount), LoadedModel->triangleCount );
	for ( int i =0; i < LoadedModel->vertexCount; i++ )
		fprintf( stdout, "Position X=%f Y=%f Z=%f \n", LoadedModel->vertices[i].position.x,
				 LoadedModel->vertices[i].position.y, LoadedModel->vertices[i].position.z);
	for ( int i =0; i < (LoadedModel->triangleCount * 3); i++ )
		fprintf( stdout, "Indexes[%d] = %u \n", i, LoadedModel->indexes[i]);
#endif

    GLuint PositionBufferName;
    glGenBuffers(1, &PositionBufferName);
    glBindBuffer(GL_ARRAY_BUFFER, PositionBufferName);
    glBufferData(GL_ARRAY_BUFFER, LoadedModel->vertexCount * sizeof( Vertex ) , LoadedModel->vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

#ifdef DEBUG_DRAW
    fprintf( stdout, " glBindBuffer(GL_ARRAY_BUFFER, 0);\n" );
#endif

    glGenVertexArrays(1, &VertexArrayName);
    glBindVertexArray(VertexArrayName);
		glBindBuffer(GL_ARRAY_BUFFER, PositionBufferName);
		UINT uiPosElCount =  ELEMENTS_IN_STRUCTURE(Vertex::position,float);
		UINT uiColElCount =  ELEMENTS_IN_STRUCTURE(Vertex::normal,float);
		UINT uiTexElCount =  ELEMENTS_IN_STRUCTURE(Vertex::texture,float);
		glVertexAttribPointer(0, uiPosElCount, GL_FLOAT, GL_FALSE, sizeof( Vertex ), GLF_BUFFER_OFFSET(0));
		glVertexAttribPointer(1, uiColElCount, GL_FLOAT, GL_FALSE, sizeof( Vertex ), GLF_BUFFER_OFFSET( sizeof(Vertex::position) ) );
		glVertexAttribPointer(2, uiTexElCount, GL_FLOAT, GL_FALSE, sizeof( Vertex ), GLF_BUFFER_OFFSET( sizeof(Vertex::position) + sizeof(Vertex::normal) ) );
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		glEnableVertexAttribArray(0);
		glEnableVertexAttribArray(1);
		glEnableVertexAttribArray(2);

    glBindVertexArray(0);

    FrameBufferInitialization();

#ifdef DEBUG_DRAW
    fprintf( stdout, " glBindVertexArray(0);\n" );
#endif
//-----------------------------------------------------------
   for ( int i = 0; i < OBJECTS_COUNT; i++ )
   {
	   ModelPositions[i] = glm::mat4(1.0f);
	   ModelPositions[i] = glm::translate(ModelPositions[i], glm::vec3(0.0f, 0.0f, -0.5f * i ));
   }

    bool done = false;
    SDL_Event event;

    while ( !done )
    {
        while ( SDL_PollEvent ( &event ) )
        {
        	switch ( event.type )
        	{
        	break;case SDL_QUIT:
        	{
                done=true;
                bDrawGo = false;
            }
        	break;case SDL_KEYDOWN:
            {
            	switch (event.key.keysym.sym)
            	{
            	break;case SDLK_ESCAPE:
            		fprintf(stdout, "Finishing program\n" );
                    done=true;

                break;case SDLK_SPACE:
				{
					//SmdModel *LoadedModel = LoadSMDModel("./models/cube2.SMD");
#ifdef DEBUG
		            fprintf(stdout, "LoadedModel->vertexCount = %u\n",  LoadedModel->vertexCount);
		            fprintf(stdout, "LoadedModel->triangleCount = %d\n", LoadedModel->triangleCount  );
#endif
						//boost::thread my_thread(&Draw);
						// my_thread.join();
				}
				break;case SDLK_UP:
				{
					ModelPositions[0] = glm::translate(ModelPositions[0], glm::vec3(0.0f, 0.0f, 0.1f));
				}
				break;case SDLK_DOWN:
				{
					ModelPositions[0] = glm::translate(ModelPositions[0], glm::vec3(0.0f, 0.0f, -0.1f));
				}
				break;case SDLK_RIGHT:
				{
					ModelPositions[0] = glm::rotate(ModelPositions[0], 1.0f , glm::vec3(1.0f, 0.0f, 0.0f));
				}
				break;case SDLK_LEFT:
				{
					ModelPositions[0] = glm::rotate(ModelPositions[0], -1.0f , glm::vec3(1.0f, 0.0f, 0.0f));
				}
				break;case SDLK_w:
            	{
            		GameCamera.Translate( 0.0f, 0.0f, 1.0f );
            	}
            	break;default:
            	{
            	}
                };//switch
            }
        	break;case SDL_MOUSEMOTION:
            {
            	if ( event.motion.state & SDL_BUTTON( SDL_BUTTON_LEFT ) )
            	{
            		GameCamera.RotateEyeAroundCenter( event.motion.xrel * MOUSE_SENSETIVITY,
            				                          event.motion.yrel * MOUSE_SENSETIVITY, 0.0f );
            	}
            }
            break;case SDL_MOUSEBUTTONDOWN:
            {

            }
            break;case SDL_MOUSEBUTTONUP:
        	{

        	}
        	break;default:
        	{

        	}
        }
        }
        Draw();
    }
    delete MyFrameBuffer;
    delete MyRenderBuffer;
    delete MyTextureBuffer;

    glDeleteBuffers(1, &PositionBufferName);
    glDeleteProgram(ProgramName);
    glDeleteVertexArrays(1, &VertexArrayName);

    SDL_Quit();

    return 0;
}
